package com.yunji.common.utils.http;

import com.yunji.common.constant.General;
import com.yunji.common.utils.bean.BeanUtils;
import com.yunji.common.utils.StringUtils;
import com.yunji.common.utils.uuid.UUIDUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.nio.file.Files;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Map;

import static com.yunji.common.constant.Constants.STR_NULL;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * 通用http发送方法
 * 
 * @author yunji
 */
public class HttpUtils
{
    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);

    private static final String HTTP_HEAD_ACCEPT = "accept";

    private static final String HTTP_HEAD_CHARSET = "Accept-Charset";

    private static final String HTTP_HEAD_CONNECTION = "connection";

    private static final String HTTP_HEAD_USER_AGENT = "user-agent";

    private static final String HTTP_HEAD_CONTENT_TYPE = "content-type";

    private static final String HTTP_PARAM_ACCEPT = "*/*";

    private static final String HTTP_PARAM_CONNECTION = "Keep-Alive";

    private static final String HTTP_PARAM_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)";

    private static final String HTTP_PARAM_CHARSET = "utf-8";

    private static final String HTTP_PARAM_CONTENT_TYPE_JSON = "application/json; charset=utf-8";


    private interface HTTPHead{
        void properties(URLConnection conn);
    }

    private HttpUtils() { }

    private static HTTPHead defaultGet()
    {
        return conn->
        {
            conn.setRequestProperty(HTTP_HEAD_ACCEPT, HTTP_PARAM_ACCEPT);
            conn.setRequestProperty(HTTP_HEAD_CONNECTION, HTTP_PARAM_CONNECTION);
            conn.setRequestProperty(HTTP_HEAD_USER_AGENT, HTTP_PARAM_USER_AGENT);
        };
    }

    private static HTTPHead defaultPost()
    {
        return conn->
        {
            conn.setRequestProperty(HTTP_HEAD_ACCEPT, HTTP_PARAM_ACCEPT);
            conn.setRequestProperty(HTTP_HEAD_CONNECTION, HTTP_PARAM_CONNECTION);
            conn.setRequestProperty(HTTP_HEAD_USER_AGENT, HTTP_PARAM_USER_AGENT);
            conn.setRequestProperty(HTTP_HEAD_CHARSET, HTTP_PARAM_CHARSET);
            conn.setRequestProperty(HTTP_HEAD_CONTENT_TYPE, HTTP_PARAM_CHARSET);
        };
    }

    private static HTTPHead defaultJSON()
    {
        return conn->
        {
            conn.setRequestProperty(HTTP_HEAD_ACCEPT, HTTP_PARAM_ACCEPT);
            conn.setRequestProperty(HTTP_HEAD_CONNECTION, HTTP_PARAM_CONNECTION);
            conn.setRequestProperty(HTTP_HEAD_USER_AGENT, HTTP_PARAM_USER_AGENT);
            conn.setRequestProperty(HTTP_HEAD_CONTENT_TYPE, HTTP_PARAM_CONTENT_TYPE_JSON);
        };
    }

    /**
     * @Title	get
     * @Desc	无参get请求
     * @Date	2024-08-09 20:11:45.802
     * @param	url 请求地址
     * @Return	String 响应结果
     */
    public static String get(String url)
    {
        return get(url, STR_NULL);
    }

    /**
     * @Title	get
     * @Desc	带参get请求(响应结果UTF-8)
     * @Date	2024-08-09 20:12:48.835
     * @param	url 请求地址
     * @param	param 请求参数
     * @Return	String 响应结果
     */
    public static String get(String url, String param)
    {
        return get(url, param, General.UTF8);
    }

    /**
     * @Title	get
     * @Desc	带参get请求(响应结果自定义编码集)
     * @Date	2024-08-09 20:13:48.734
     * @param	url 请求地址
     * @param	param 请求参数
     * @param	charset 编码类型
     * @Return	String 响应结果
     */
    public static String get(String url, String param, String charset)
    {
        return get(url, param, charset, defaultGet());
    }

    /**
     * @Title	get
     * @Desc	向指定 URL 发送GET方法的请求
     * @Date	2024-08-09 20:10:21.770
     * @param	url 发送请求的 URL
     * @param	param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @param	charset 编码类型
     * @param	head 请求头属性实现类
     * @Return	String 所代表远程资源的响应结果
     */
    private static String get(String url, String param, String charset, HTTPHead head)
    {
        StringBuilder result = new StringBuilder();
        URLConnection conn = null;
        try
        {
            String address = BeanUtils.isNotNull(param) ? url + "?" + param : url;
            URL uri = new URL(address);
            conn = uri.openConnection();
            head.properties(conn);
            conn.connect();
        }
        catch (ConnectException e)
        {
            log.error("构建【HTTP:GET】ConnectException, url={}, param={}", url, param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("构建【HTTP:GET】SocketTimeoutException, url={}, param={}", url, param, e);
        }
        catch (IOException e)
        {
            log.error("构建【HTTP:GET】IOException, url={}, param={}", url, param, e);
        }
        catch (Exception e)
        {
            log.error("构建【HTTP:GET】Exception, url={}, param={}", url, param, e);
        }

        Assert.isTrue((conn != null), StringUtils.format("build get:[{}]  request failure!", url));
        try(InputStream stream = conn.getInputStream() )
        {
            BufferedReader reader = new BufferedReader(new InputStreamReader(stream, charset));
            String line;
            while (BeanUtils.isNotNull(line = reader.readLine())) { result.append(line); }
        }
        catch (IOException e)
        {
            log.error("响应【HTTP:GET】 IOException, url={}, param={}", url, param, e);
        }
        log.info("【HTTP:GET】响应结果:{}", result);
        return result.toString();
    }

    /**
     * @Title	form
     * @Desc	POST请求(表单)
     * @Date	2024-08-09 20:14:34.981
     * @param	url 请求地址
     * @param	param 请求参数
     * @Return	String 响应结果
     */
    public static String form(String url, String param)
    {
        return post(url, param, defaultPost());
    }

    /**
     * @Title	json
     * @Desc	POST请求(JSON)
     * @Date	2024-08-09 20:15:10.936
     * @param	url 请求地址
     * @param	param 请求参数
     * @Return	String 响应结果
     */
    public static String json(String url, String param)
    {
        return post(url, param, defaultJSON());
    }

    // 前缀
    private static final String PREFIX = "--";
    // 换行符
    private static final String CRLF = "\r\n";

    /**
     * @Title	writeParams
     * @Desc	写入参数
     * @Date	2024-08-13 10:40:37.078
     * @param	params 参数对象
     * @param	stream 写入流
     * @param	BOUNDARY 分界符
     */
    private static void writeParams(Map<String, Object> params, DataOutputStream stream, final String BOUNDARY) throws IOException
    {
        if (BeanUtils.isNull(params)) {return;}
        for (Map.Entry<String, Object> param : params.entrySet())
        {
            // 键，相当于上面分析的请求中的username
            String k = param.getKey();

            // 值，相当于上面分析的请求中的sdafdsa
            Object v = param.getValue();

            // 像请求体中写分割线，就是前缀+分界线+换行
            stream.writeBytes(PREFIX + BOUNDARY + CRLF);
            // 拼接参数名，格式就是Content-Disposition: form-data; name="key" 其中key就是当前循环的键值对的键
            stream.writeBytes("Content-Disposition: form-data; " + "name=\"" + k + "\"" + CRLF);
            // 空行，一定不能少，键和值之间有一个固定的空行
            stream.writeBytes(CRLF);
            // 将值写入,用字节流防止乱码
            stream.write(v.toString().getBytes(UTF_8));
            // 换行
            stream.writeBytes(CRLF);
        }
    }

    /**
     * @Title	writeFiles
     * @Desc	写入文件
     * @Date	2024-08-13 10:41:26.796
     * @param	file 文件信息
     * @param	stream 写入流信息
     * @param	fileName 文件名
     * @param	BOUNDARY 分界符
     */
    private static void writeFiles(File file, DataOutputStream stream, String fileName, final String BOUNDARY) throws IOException
    {
        StringBuilder build = getBuild(file, fileName, BOUNDARY);

        // 传输中文名附件时防止乱码
        stream.write(build.toString().getBytes(UTF_8));

        byte[] buffer = new byte[1024];
        int bytesRead = -1;
        InputStream inputStream = Files.newInputStream(file.toPath());
        while ((bytesRead = inputStream.read(buffer)) != -1)
        {
            stream.write(buffer, 0, bytesRead);
        }
        inputStream.close();
        stream.writeBytes(CRLF);
    }

    /**
     * @Title	getBuild
     * @Desc	获取构建文件信息
     * @Date	2024-08-13 10:57:50.129
     * @param	file 文件信息
     * @param	fileName 文件参数名称
     * @param	BOUNDARY 分界符
     * @Return	StringBuilder
     */
    private static StringBuilder getBuild(File file, String fileName, String BOUNDARY)
    {
        StringBuilder build = new StringBuilder();
        build.append(PREFIX);
        build.append(BOUNDARY);
        build.append(CRLF);
        build.append("Content-Disposition: form-data; name=\"");
        build.append(fileName);
        build.append("\"; filename=\"");
        build.append(file.getName());
        build.append("\"");
        build.append(CRLF);
        build.append("Content-Type: application/octet-stream; charset=UTF-8");
        build.append(CRLF);
        // 空行
        build.append(CRLF);
        return build;
    }

    /**
     * @Title	json
     * @Desc	POST请求(JSON)
     * @Date	2024-08-09 20:15:10.936
     * @param	url 请求地址
     * @param	params 请求参数
     * @Return	String 响应结果
     */
    public static String file(String url, Map<String, Object> params, String fileName, File file)
    {
        log.info("【HTTP:文件】请求地址:{},请求参数:{}", url, params);
        // 定界符
        final String BOUNDARY = UUIDUtils.fastUUID();

        HttpURLConnection conn = null;
        try
        {
            URL uri = new URL(url);
            conn = (HttpURLConnection) uri.openConnection();
            conn.setDoInput(true);// 允许输入
            conn.setDoOutput(true); // 设置为输出模式
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY);
        }
        catch (ConnectException e)
        {
            log.error("构建【HTTP:文件】ConnectException, url={}, param={}", url, params, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("构建【HTTP:文件】SocketTimeoutException, url={}, param={}", url, params, e);
        }
        catch (IOException e)
        {
            log.error("构建【HTTP:文件】IOException, url={}, param={}", url, params, e);
        }
        catch (Exception e)
        {
            log.error("构建【HTTP:文件】Exception, url={}, param={}", url, params, e);
        }

        Assert.isTrue((conn != null), "构建连接失败");

        try(DataOutputStream stream = new DataOutputStream(conn.getOutputStream()))
        {

            // 发送参数数据
            writeParams(params, stream, BOUNDARY);

            writeFiles(file, stream, fileName, BOUNDARY);

            // 请求结束标志
            stream.writeBytes(PREFIX + BOUNDARY + PREFIX + CRLF);
            stream.flush();
        }
        catch (IOException e)
        {
            log.error("写参【HTTP:文件】IOException, url={}, param={}", url, params, e);
        }

        StringBuilder result = new StringBuilder();
        try(BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), UTF_8));)
        {
            String line;
            while (BeanUtils.isNotNull(line = in.readLine())) {result.append(line);}
        }
        catch (IOException e)
        {
            log.error("响应【HTTP:文件】IOException, url={}, param={}", url, params, e);
        }
        conn.disconnect();
        log.info("【HTTP:文件】响应结果:{}", result);
        return result.toString();
    }


    /**
     * @Title	post
     * @Desc	向指定 URL 发送POST方法的请求
     * @Date	2024-08-09 19:43:28.503
     * @param	url 发送请求的 URL
     * @param	param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @Return	String 所代表远程资源的响应结果
     */
    private static String post(String url, String param, HTTPHead head)
    {
        log.info("【HTTP:POST】请求地址:{},请求参数:{}", url, param);
        URLConnection conn = null;
        try
        {
            URL uri = new URL(url);
            conn = uri.openConnection();
            head.properties(conn);
            conn.setDoOutput(true);
            conn.setDoInput(true);
        }
        catch (ConnectException e)
        {
            log.error("构建【HTTP:POST】ConnectException, url={}, param={}", url, param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("构建【HTTP:POST】SocketTimeoutException, url={}, param={}", url, param, e);
        }
        catch (IOException e)
        {
            log.error("构建【HTTP:POST】IOException, url={}, param={}", url, param, e);
        }
        catch (Exception e)
        {
            log.error("构建【HTTP:POST】Exception, url={}, param={}", url, param, e);
        }

        Assert.isTrue((conn != null), StringUtils.format("build post:[{}]  request failure!", url));

        try(PrintWriter out = new PrintWriter(conn.getOutputStream()))
        {

            out.print(param);
            out.flush();
        }
        catch (IOException e)
        {
            log.error("写参【HTTP:POST】IOException, url={}, param={}", url, param, e);
        }

        StringBuilder result = new StringBuilder();
        try(BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), UTF_8));)
        {
            String line;
            while (BeanUtils.isNotNull(line = in.readLine())) {result.append(line);}
        }
        catch (IOException e)
        {
            log.error("响应【HTTP:POST】IOException, url={}, param={}", url, param, e);
        }
        log.info("【HTTP:POST】响应结果:{}", result);
        return result.toString();
    }

    /**
     * @Title	ssl
     * @Desc	HTTPS请求
     * @Date	2024-08-09 20:16:20.306
     * @param	url 请求地址
     * @param	param 请求参数
     * @Return	String 响应结果
     */
    public static String https(String url, String param)
    {
        return https(url, param, defaultPost());
    }

    /**
     * @Title	ssl
     * @Desc	HTTPS请求
     * @Date	2024-08-09 20:16:20.306
     * @param	url 请求地址
     * @param	param 请求参数
     * @param	head 请求头参数设置
     * @Return	String 响应结果
     */
    private static String https(String url, String param, HTTPHead head)
    {
        StringBuilder result = new StringBuilder();
        String urlNameString = url + "?" + param;
        log.info("【HTTP:POST】请求地址:{}", url);
        try
        {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new SecureRandom());
            URL console = new URL(urlNameString);
            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
            head.properties(conn);
            conn.setDoOutput(true);
            conn.setDoInput(true);

            conn.setSSLSocketFactory(sc.getSocketFactory());
            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
            conn.connect();
            InputStream is = conn.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String ret;
            while (BeanUtils.isNotNull(ret = br.readLine()))
            {
                result.append(new String(ret.getBytes(ISO_8859_1), UTF_8));
            }
            log.info("【HTTPS:POST】响应结果:{}", result);
            conn.disconnect();
            br.close();
        }
        catch (ConnectException e)
        {
            log.error("【HTTPS:POST】ConnectException, url={}, param={}", url, param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("【HTTPS:POST】SocketTimeoutException, url={}, param={}", url, param, e);
        }
        catch (IOException e)
        {
            log.error("【HTTPS:POST】IOException, url={}, param={}", url, param, e);
        }
        catch (Exception e)
        {
            log.error("【HTTPS:POST】Exception, url={}, param={}", url, param, e);
        }
        return result.toString();
    }

    private static class TrustAnyTrustManager implements X509TrustManager
    {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
        {
            // Not Do Things
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)
        {
            // Not Do Things
        }

        @Override
        public X509Certificate[] getAcceptedIssuers()
        {
            return new X509Certificate[] {};
        }
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier
    {
        @Override
        public boolean verify(String hostname, SSLSession session)
        {
            return true;
        }
    }
}